﻿@page "/logging/auth"
@using AliasVault.RazorComponents.Tables
@using AliasVault.Shared.Models.Enums
@using Microsoft.AspNetCore.WebUtilities
@inherits MainBase
@inject NavigationManager Navigation
@implements IDisposable

<LayoutPageTitle>Auth logs</LayoutPageTitle>

<PageHeader
    BreadcrumbItems="@BreadcrumbItems"
    Title="@GetTitle()"
    Description="This page shows an overview of recent auth attempts.">
    <CustomActions>
        <DeleteButton OnClick="DeleteLogsWithConfirmation" ButtonText="Delete all logs" />
        <RefreshButton OnClick="() => RefreshData(CancellationToken.None)" ButtonText="Refresh" />
    </CustomActions>
</PageHeader>

@if (IsInitialized)
{
    <div class="px-4">
        <ResponsivePaginator CurrentPage="CurrentPage" PageSize="PageSize" TotalRecords="TotalRecords" OnPageChanged="HandlePageChanged" />

       <div class="mb-3 flex space-x-4">
            <div class="flex w-full">
                <div class="w-1/2 pr-2">
                    <div class="relative">
                        <SearchIcon />
                        <input type="text" @bind-value="SearchTerm" @bind-value:event="oninput" id="search" placeholder="Search logs..." class="w-full px-4 ps-10 py-2 border rounded text-sm text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white">
                    </div>
                </div>
                <div class="w-1/4 px-1">
                    <select @bind="SelectedEventType" class="w-full px-4 py-2 border rounded text-sm text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white">
                        <option value="">All event types</option>
                        @foreach (var eventType in Enum.GetValues<AuthEventType>())
                        {
                            <option value="@eventType">@eventType</option>
                        }
                    </select>
                </div>
                <div class="w-1/4 pl-2">
                    <button type="button" @onclick="ToggleUniqueUsernames"
                            class="w-full px-4 py-2 text-sm font-medium rounded border transition-colors duration-200 @(ShowUniqueUsernames ? "bg-orange-400 text-white border-orange-500 hover:bg-orange-600" : "bg-white text-gray-700 border-gray-300 hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-600")">
                        @(ShowUniqueUsernames ? "✓ Unique users" : "Unique users")
                    </button>
                </div>
            </div>
        </div>
    </div>
}

@if (IsLoading)
{
    <LoadingIndicator />
}
else
{
    <div class="px-4">
        <SortableTable Columns="@_tableColumns" SortColumn="@SortColumn" SortDirection="@SortDirection" OnSortChanged="HandleSortChanged">
            @foreach (var log in LogList)
            {
                <SortableTableRow>
                    <SortableTableColumn IsPrimary="true">@log.Id</SortableTableColumn>
                    <SortableTableColumn>@log.Timestamp.ToString("yyyy-MM-dd HH:mm")</SortableTableColumn>
                    <SortableTableColumn>
                        @if (UserLookup.TryGetValue(log.Username, out var userId))
                        {
                            <a href="users/@userId">
                                @log.Username
                            </a>
                        }
                        else
                        {
                            <span class="italic">@log.Username</span>
                        }
                    </SortableTableColumn>
                    <SortableTableColumn>@log.Client</SortableTableColumn>
                    <SortableTableColumn>@log.EventType</SortableTableColumn>
                    <SortableTableColumn><StatusPill Enabled="log.IsSuccess" TextTrue="Success" TextFalse="@log.FailureReason.ToString()" /></SortableTableColumn>
                    <SortableTableColumn>
                        @if (!string.IsNullOrEmpty(log.IpAddress))
                        {
                            <a href="logging/auth?search=@Uri.EscapeDataString(log.IpAddress)" class="cursor-pointer">@log.IpAddress</a>
                        }
                        else
                        {
                            <span>-</span>
                        }
                    </SortableTableColumn>
                </SortableTableRow>
            }
        </SortableTable>
    </div>
}

@code {
    private readonly List<TableColumn> _tableColumns = [
        new TableColumn { Title = "ID", PropertyName = "Id" },
        new TableColumn { Title = "Time", PropertyName = "Timestamp" },
        new TableColumn { Title = "Username", PropertyName = "Username" },
        new TableColumn { Title = "Client", PropertyName = "Client" },
        new TableColumn { Title = "Event", PropertyName = "EventType" },
        new TableColumn { Title = "Success", PropertyName = "IsSuccess" },
        new TableColumn { Title = "IP", PropertyName = "IpAddress" },
    ];

    private List<AuthLog> LogList { get; set; } = [];
    private Dictionary<string, string> UserLookup { get; set; } = [];
    private bool IsInitialized { get; set; } = false;
    private bool IsLoading { get; set; } = true;
    private int CurrentPage { get; set; } = 1;
    private int PageSize { get; set; } = 50;
    private int TotalRecords { get; set; }

    private string _searchTerm = string.Empty;
    private string _lastSearchTerm = string.Empty;
    private CancellationTokenSource? _searchCancellationTokenSource;

    private string SearchTerm
    {
        get => _searchTerm;
        set
        {
            if (_searchTerm != value)
            {
                _searchTerm = value;
                _searchCancellationTokenSource?.Cancel();
                _searchCancellationTokenSource = new CancellationTokenSource();
                _ = RefreshData(_searchCancellationTokenSource.Token);
            }
        }
    }

    private string _selectedEventType = string.Empty;
    private string SelectedEventType
    {
        get => _selectedEventType;
        set
        {
            if (_selectedEventType != value)
            {
                _selectedEventType = value;
                _searchCancellationTokenSource?.Cancel();
                _searchCancellationTokenSource = new CancellationTokenSource();
                _ = RefreshData(_searchCancellationTokenSource.Token);
            }
        }
    }

    private bool ShowUniqueUsernames { get; set; } = false;

    private void ToggleUniqueUsernames()
    {
        ShowUniqueUsernames = !ShowUniqueUsernames;
        _ = RefreshData(CancellationToken.None);
    }

    private string SortColumn { get; set; } = "Id";
    private SortDirection SortDirection { get; set; } = SortDirection.Descending;

    private async Task HandleSortChanged((string column, SortDirection direction) sort)
    {
        SortColumn = sort.column;
        SortDirection = sort.direction;
        await RefreshData(CancellationToken.None);
    }

    /// <inheritdoc />
    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();

        BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = "Auth logs" });

        Navigation.LocationChanged += OnLocationChanged;
        ParseQueryAndRefresh();
        await RefreshData(CancellationToken.None);
    }

    private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
    {
        ParseQueryAndRefresh();
    }

    private void ParseQueryAndRefresh()
    {
        var uri = Navigation.ToAbsoluteUri(Navigation.Uri);
        var queryParameters = QueryHelpers.ParseQuery(uri.Query);

        if (queryParameters.TryGetValue("search", out var searchValue))
        {
            _searchTerm = searchValue.FirstOrDefault() ?? string.Empty;
        }
        else
        {
            _searchTerm = string.Empty;
        }

        if (_searchTerm != _lastSearchTerm)
        {
            _lastSearchTerm = _searchTerm;
            _ = RefreshData(CancellationToken.None); // Fire and forget
        }
    }

    /// <inheritdoc />
    public void Dispose()
    {
        Navigation.LocationChanged -= OnLocationChanged;
        _searchCancellationTokenSource?.Cancel();
        _searchCancellationTokenSource?.Dispose();
    }

    private void HandlePageChanged(int newPage)
    {
        CurrentPage = newPage;
        _ = RefreshData(CancellationToken.None);
    }

    private async Task RefreshData(CancellationToken cancellationToken = default)
    {
        try
        {
            IsLoading = true;
            StateHasChanged();

            await using var dbContext = await DbContextFactory.CreateDbContextAsync(cancellationToken);
            var query = dbContext.AuthLogs.AsQueryable();

            if (!string.IsNullOrEmpty(SearchTerm))
            {
                // Reset page number back to 1 if the search term has changed.
                if (SearchTerm != _lastSearchTerm)
                {
                    CurrentPage = 1;
                }
                _lastSearchTerm = SearchTerm;

                // If the search term starts with "client:", we search for the client header.
                if (SearchTerm.StartsWith("client:", StringComparison.OrdinalIgnoreCase))
                {
                    var clientSearchTerm = SearchTerm.Substring(7).ToLower();
                    query = query.Where(x => EF.Functions.Like((x.Client ?? string.Empty).ToLower(), "%" + clientSearchTerm + "%"));
                }
                else
                {
                    var searchTerm = SearchTerm.Trim().ToLower();
                    query = query.Where(x => EF.Functions.Like((x.Username ?? string.Empty).ToLower(), "%" + searchTerm + "%") ||
                                             EF.Functions.Like((x.IpAddress ?? string.Empty).ToLower(), "%" + searchTerm + "%"));
                }
            }

            if (!string.IsNullOrEmpty(SelectedEventType))
            {
                var success = Enum.TryParse<AuthEventType>(SelectedEventType, out var eventType);
                if (success)
                {
                    query = query.Where(x => x.EventType == eventType);
                }
            }

            query = ApplySort(query);

            // Handle unique usernames filtering after getting all results (since GroupBy with OrderBy in Select is complex for EF)
            if (ShowUniqueUsernames)
            {
                // Get all matching records first
                var allLogs = await query.ToListAsync(cancellationToken);

                if (cancellationToken.IsCancellationRequested)
                {
                    return;
                }

                // Group by username and take the latest entry for each username
                var uniqueLogs = allLogs
                    .GroupBy(x => x.Username)
                    .Select(g => g.OrderByDescending(x => x.Timestamp).First())
                    .ToList();

                // Apply pagination to the unique results
                TotalRecords = uniqueLogs.Count;
                LogList = uniqueLogs
                    .Skip((CurrentPage - 1) * PageSize)
                    .Take(PageSize)
                    .ToList();
            }
            else
            {
                TotalRecords = await query.CountAsync(cancellationToken);
                LogList = await query
                    .Skip((CurrentPage - 1) * PageSize)
                    .Take(PageSize)
                    .ToListAsync(cancellationToken);

                if (cancellationToken.IsCancellationRequested)
                {
                    return;
                }
            }

            // Create user lookup dictionary for the current page
            var usernames = LogList.Select(x => x.Username).Distinct().Where(x => !string.IsNullOrEmpty(x)).ToList();
            var users = await dbContext.AliasVaultUsers
                .Where(u => u.UserName != null && usernames.Contains(u.UserName))
                .Select(u => new { u.UserName, u.Id })
                .ToListAsync(cancellationToken);

            UserLookup = users.Where(u => u.UserName != null).ToDictionary(u => u.UserName!, u => u.Id);

            IsLoading = false;
            IsInitialized = true;
            StateHasChanged();
        }
        catch (OperationCanceledException)
        {
            // Expected when cancellation is requested, do nothing
        }
    }

    /// <summary>
    /// Apply sort to the query.
    /// </summary>
    private IQueryable<AuthLog> ApplySort(IQueryable<AuthLog> query)
    {
        // Apply sort.
        switch (SortColumn)
        {
            case "Timestamp":
                query = SortDirection == SortDirection.Ascending
                    ? query.OrderBy(x => x.Timestamp)
                    : query.OrderByDescending(x => x.Timestamp);
                break;
            case "Username":
                query = SortDirection == SortDirection.Ascending
                    ? query.OrderBy(x => x.Username)
                    : query.OrderByDescending(x => x.Username);
                break;
            case "Client":
                query = SortDirection == SortDirection.Ascending
                    ? query.OrderBy(x => x.Client)
                    : query.OrderByDescending(x => x.Client);
                break;
            case "EventType":
                query = SortDirection == SortDirection.Ascending
                    ? query.OrderBy(x => x.EventType)
                    : query.OrderByDescending(x => x.EventType);
                break;
            case "IsSuccess":
                query = SortDirection == SortDirection.Ascending
                    ? query.OrderBy(x => x.IsSuccess)
                    : query.OrderByDescending(x => x.IsSuccess);
                break;
            case "IpAddress":
                query = SortDirection == SortDirection.Ascending
                    ? query.OrderBy(x => x.IpAddress)
                    : query.OrderByDescending(x => x.IpAddress);
                break;
            default:
                query = SortDirection == SortDirection.Ascending
                    ? query.OrderBy(x => x.Id)
                    : query.OrderByDescending(x => x.Id);
                break;
        }

        return query;
    }

    /// <summary>
    /// Show confirmation modal to delete all logs.
    /// </summary>
    private async Task DeleteLogsWithConfirmation()
    {
        if (await ConfirmModalService.ShowConfirmation("Confirm Delete", "Are you sure you want to delete all logs? This action cannot be undone."))
        {
            await DeleteLogs();
        }
    }

    /// <summary>
    /// Delete all logs.
    /// </summary>
    private async Task DeleteLogs()
    {
        IsLoading = true;
        StateHasChanged();

        await using var dbContext = await DbContextFactory.CreateDbContextAsync();
        dbContext.AuthLogs.RemoveRange(dbContext.AuthLogs);
        await dbContext.SaveChangesAsync();
        await RefreshData(CancellationToken.None);

        IsLoading = false;
        StateHasChanged();
    }

    /// <summary>
    /// Get the title of the page.
    /// </summary>
    /// <returns>The title of the page.</returns>
    private string GetTitle()
    {
        if (TotalRecords == 0)
        {
            return "Auth logs";
        }

        var title = $"Auth logs ({TotalRecords:N0})";
        if (ShowUniqueUsernames)
        {
            title += " - Unique usernames";
        }

        return title;
    }
}
